package com.dynamic.mybatis.core.generator;

import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.dynamic.mybatis.core.generator.method.*;
import com.dynamic.mybatis.core.metadata.DynamicMappedStatement;
import com.dynamic.mybatis.core.metadata.MappedStatementTable;
import com.dynamic.mybatis.core.toolkit.XmlUtil;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @Description
 * @Author xs
 * @Date 2023/4/14 10:47
 */
public abstract class InjectorMethod  implements Constants {

    protected String SPACE = "\t";
    protected MapperMethod mapperMethod;
    protected Logger logger;


    //key sqlMethod   value sql
    protected static Map<String,String> sqlMethodExtended = new HashMap<>();
    //key com.dynamic.mybatis.core.generator.MapperMethod.name()
    protected static Map<String,InjectorMethod> injectorMapperMethods = new LinkedHashMap<>();

    static {
        addInjectorMethod(new Save());
        addInjectorMethod(new SelectPage());
        addInjectorMethod(new SelectById());
        addInjectorMethod(new SelectList());
        addInjectorMethod(new DeleteById());
        addInjectorMethod(new Insert());
        addInjectorMethod(new Update());
        addInjectorMethod(new UpdateById());
        addInjectorMethod(new InsertBatch());
        addInjectorMethod(new UpdateBatch());
        addInjectorMethod(new SaveBatch());
        //扩展方式
        //InjectorMethod.put(MapperMethod.upsert.sqlMethod,"<script>\nUPSERT INTO %s %s VALUES %s\n</script>");
        //ddInjectorMethod(new Upsert());
    }

    private static void addInjectorMethod(InjectorMethod injectorMethod){
        injectorMapperMethods.put(injectorMethod.mapperMethod.name(),injectorMethod);

        String sqlMethod = injectorMethod.mapperMethod.getSqlMethod();
        if(sqlMethod != null){
            String sql= null;
            for(SqlMethod sm : SqlMethod.values()){
                if(sqlMethod.equals(sm.getMethod())){
                    sql =  sm.getSql();
                    break;
                }
            }
            put(injectorMethod.mapperMethod.getSqlMethod(),sql);
        }
    }

    public static void put(String sqlMethod,String sql){
        sqlMethodExtended.put(sqlMethod,sql);
    }


    public static <T extends InjectorMethod > T   getByMapperMethod(String mapperMethod){
        return  (T) injectorMapperMethods.get(mapperMethod);
    }

    public  String getSqlTemplate(){
        String sql =  sqlMethodExtended.get(mapperMethod.getSqlMethod());
        if(StringUtils.isBlank(sql)){
            for(SqlMethod sm : SqlMethod.values()){
                if(mapperMethod.getSqlMethod().equalsIgnoreCase(sm.getMethod())){
                    sqlMethodExtended.put(sm.getMethod(),sm.getSql());
                    return sm.getSql();
                }
            }
        }
        return sql;
    }



    public InjectorMethod(MapperMethod mapperMethod) {
        this.mapperMethod = mapperMethod;
    }

    /**
     * 注入自定义方法
     */
    public DynamicMappedStatement inject(String namespace,MapperBuilderAssistant assistant, MappedStatementTable table) {
        assistant.setCurrentNamespace(namespace);
        /* 注入自定义方法 */
      return  injectMappedStatement(namespace,table,assistant);
    }

    public String replaceScriptTag(String sql){
         return    sql.replaceAll("^<script>\\s*|\\s*</script>$", "");
    }


    public String builderMapperXml(String sql,MappedStatementTable table,MappedStatement mappedStatement){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(LEFT_CHEV);
        String tagName = mappedStatement.getSqlCommandType().name().toLowerCase();
        stringBuilder.append(tagName);
        String id = mapperMethod+table.getTableName().substring(0,1).toUpperCase()+table.getTableName().substring(1);
        stringBuilder.append(" id='"+id+"' ");
        stringBuilder.append(" parameterType='map' ");
        if(StringUtils.isNotBlank(mapperMethod.getResultType())){
            stringBuilder.append(" resultType='"+mapperMethod.getResultType()+"' ");
        }
        if(mapperMethod.getReturnType() != null){
            stringBuilder.append(" returnType = '"+mapperMethod.getReturnType().name().toLowerCase()+"' ");
        }
        if(mapperMethod.isBatch()){
            stringBuilder.append(" batchExecute = 'true' ");
        }

        if(StringUtils.isNotBlank(mapperMethod.getRemarks())){
            stringBuilder.append(" name='"+String.format(mapperMethod.getRemarks(), Objects.toString(table.getName(), ""))+"' ");
        }
        stringBuilder.append(RIGHT_CHEV);
        stringBuilder.append(NEWLINE);
        stringBuilder.append(SPACE);
        stringBuilder.append(replaceScriptTag(sql));
        stringBuilder.append(NEWLINE);
        stringBuilder.append(LEFT_CHEV);
        stringBuilder.append(SLASH);
        stringBuilder.append(tagName);
        stringBuilder.append(RIGHT_CHEV);
        stringBuilder.append(NEWLINE);
        stringBuilder.append(NEWLINE);
        return XmlUtil.javaApacheFormatPretty(stringBuilder.toString());
    }

    public abstract String builderScriptSql(String namespace, MappedStatementTable table, MapperBuilderAssistant assistant);

    public DynamicMappedStatement builderDynamicMappedStatement(String namespace, MappedStatementTable table,MappedStatement ms,String sql){
        String shortId = mapperMethod.name()+table.getTableName().substring(0,1).toUpperCase()+table.getTableName().substring(1);
        DynamicMappedStatement.Builder dmsBuilder = new  DynamicMappedStatement().toBuilder()
                .setSqlStatement(builderMapperXml(sql,table,ms))
                .setId(shortId)
                .setNamespace(namespace)
                 .setBatchExecute(mapperMethod.isBatch())
                .setCommandType(ms.getSqlCommandType())
                .setName(String.format(Objects.toString(mapperMethod.getRemarks(),""), Objects.toString(table.getName(),"")))
                .setReturnType(mapperMethod.getReturnType());
        dmsBuilder.setMethod(mapperMethod.getMethod());
        if(ms.getResultMaps() != null && ms.getResultMaps().size() > 0){
            dmsBuilder.setResultMap(ms.getResultMaps().get(0).getId());
        }
        if(ms.getParameterMap() != null){
            dmsBuilder.setParameterMap(ms.getParameterMap().getId());
        }
        return dmsBuilder.builder();
    }


    public MappedStatement addMappedStatement(
            MapperBuilderAssistant assistant,
            MappedStatementTable table,
            String namespace,
            SqlSource sqlSource,
            SqlCommandType sqlCommandType,
            Class<?> parameterType,
            String resultMap,
            Class<?> resultType,
            KeyGenerator keyGenerator,
            String keyProperty,
            String keyColumn){

            String id =  namespace  + DOT + mapperMethod.name()+table.getTableName().substring(0,1).toUpperCase()+table.getTableName().substring(1);

            LanguageDriver languageDriver = assistant.getConfiguration().getDefaultScriptingLanguageInstance();
            Configuration configuration = assistant.getConfiguration();

            if (hasMappedStatement(configuration,id)) {
                logger.warn(LEFT_SQ_BRACKET + id + "] Has been loaded by XML or SqlProvider or Mybatis's Annotation, so ignoring this injection for [" + getClass() + RIGHT_SQ_BRACKET);
            }
            /* 缓存逻辑处理 */
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            return    assistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType,
                    null, null, null, parameterType, resultMap, resultType,
                    null,!isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn,
                    configuration.getDatabaseId(), languageDriver, null);
    }

    /**
     * 是否已经存在MappedStatement
     *
     * @param mappedStatement MappedStatement
     * @return true or false
     */
    protected boolean hasMappedStatement(Configuration configuration,String mappedStatement) {
        return configuration.hasStatement(mappedStatement, false);
    }

    public abstract DynamicMappedStatement injectMappedStatement(String namespace, MappedStatementTable table,MapperBuilderAssistant assistant);
}